# cronmon - Cron e-mail filtering and management
# Copyright (c) 2010 Crown copyright
# 
# This file is part of cronmon.
# 
# cronmon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# cronmon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with cronmon.  If not, see <http://www.gnu.org/licenses/>.

# select * from cron_message left outer join cron_message_tags as tag1 on cron_message.id = tag1.message_id where tag1.tag_id=1;

from cronmon.cron.models import Message, Tag
from actions import *

import operator


def tag_query(expression):
    """Query Messages by expression"""

    rp = RuleParser()
    base = rp.parse(expression)

    tag_map = {}
    for t in Tag.objects.filter(name__in=base.tags()):
        tag_map[t.name] = t.id
    
    raw = 'SELECT DISTINCT cron_message.* FROM cron_message'

    filtersql = node_to_sql(base, tag_map)
    if filtersql:
        raw += ' WHERE %s' % filtersql

    print raw

    return Message._default_manager.raw(raw)
    

def tag_query_sets(expression):
    rp = RuleParser()
    base = rp.parse(expression)

    tag_set_map = {
        '*': set(Message.objects.values_list('id', flat=True))
    }
    for t in Tag.objects.filter(name__in=base.tags()):
        tag_set_map[t.name] = set(t.message_set.values_list('id', flat=True))
    
    idset = node_to_set(base, tag_set_map)
    return Message.objects.filter(id__in=idset)


def node_to_set(node, tag_set_map):
    if isinstance(node, TagNode):
        name = node.name
        return tag_set_map.get(name, set()) 
    elif isinstance(node, ConjunctionNode):
        return reduce(operator.and_, [node_to_set(c, tag_set_map) for c in node.children])
    elif isinstance(node, DisjunctionNode):
        return reduce(operator.or_, [node_to_set(c, tag_set_map) for c in node.children])
    elif isinstance(node, NegationNode):
        return tag_set_map['*'] - node_to_set(node.child, tag_set_map)
    else:
        raise ValueError("Unknown node type")


def nodes_to_sql(nodes, tag_map):
    ns = []
    for n in nodes:
        s = node_to_sql(n, tag_map)
        if s:
            ns.append(s)
    return ns


def node_to_sql(node, tag_map):
    if isinstance(node, TagNode):
        name = node.name
        if name in tag_map:
            id = tag_map[name]
            return "cron_message.id IN (SELECT message_id from cron_message_tags WHERE tag_id = '%d')" % id
        return None
    elif isinstance(node, ConjunctionNode):
        return '(' + ' AND '.join(nodes_to_sql(node.children, tag_map)) + ')'
    elif isinstance(node, DisjunctionNode):
        return '(' + ' OR '.join(nodes_to_sql(node.children, tag_map)) + ')'
    elif isinstance(node, NegationNode):
        if isinstance(node.child, TagNode):
            name = node.child.name
            if name in tag_map:
                id = tag_map[name]
                return "cron_message.id NOT IN (SELECT message_id from cron_message_tags WHERE tag_id = '%d')" % id
            return None
        else:
            return 'NOT (%s)' % node_to_sql(node.child, tag_map)
    else:
        raise ValueError("Unknown node type")
